#include <tinx/device.h>
#include <tinx/fs.h>
#include <tinx/task.h>
#include <tinx/user.h>
#include <tinx/memory.h>
#include <tinx/debug.h>
#include <tinx/vsprintf.h>
#include <tinx/string.h>
#include <tinx/syscall.h>
#include <tinx/assert.h>
#include <tinx/arena.h>
#include <tinx/stdlib.h>

#define BLOCK_SIZE 1024

int char_write(inode_t *inode, void *addr, int count, int offset)
{
    assert(inode->rdev >= 0);
    int len = device_write(inode->rdev, addr, count, offset, 0);
    return len;
}

int char_read(inode_t *inode, void *addr, int count, int offset)
{
    assert(inode->rdev >= 0);
    int len = device_read(inode->rdev, addr, count, offset, 0);
    return len;
}

int block_write(inode_t *inode, void *addr, int count, int offset)
{
    task_t *task = running_task();
    if (!suser())
    {
        return -EPERM;
    }
    assert(inode->rdev >= 0);

    int stop = offset + count;
    int left = count;
    count = 0;

    while (left)
    {
        idx_t block = offset / BLOCK_SIZE;
        buffer_t *buf = bread(inode->rdev, block, BLOCK_SIZE);
        if (!buf)
            break;

        int start = offset % BLOCK_SIZE;
        int chars =  MIN(BLOCK_SIZE - start, left);

        u8 *src = (u8 *)buf->data + start;
        memcpy(src, addr, chars);
        buf->dirty = true;

        count += chars;
        offset += chars;
        addr += chars;
        left -= chars;
        brelse(buf);
    }
    if (count == 0)
        count = -EEOF;
    return count;
}

int block_read(inode_t *inode, void *addr, int count, int offset)
{
    task_t *task = running_task();
    if (!suser())
    {
        return -EPERM;
    }
    assert(inode->rdev >= 0);

    int left = count;
    count = 0;

    while (left)
    {
        idx_t block = offset / BLOCK_SIZE;
        buffer_t *buf = bread(inode->rdev, block, BLOCK_SIZE);
        if (!buf)
            break;

        int start = offset % BLOCK_SIZE;
        int chars = MIN(BLOCK_SIZE - start, left);

        u8 *src = (u8 *)buf->data + start;
        memcpy(addr, src, chars);

        count += chars;
        offset += chars;
        addr += chars;
        left -= chars;
        brelse(buf);
    }
    if (count == 0)
        count = EOF;
    return count;
}

static char buf[32];

extern file_t file_table[];

void dev_init()
{
    fs_op_t *op = fs_get_op(FS_TYPE_MINIX);

    stat_t statbuf;
    if (stat("/dev", &statbuf) != EOK)
    {
        mkdir("/dev", 0755);
    }

    dev_t dev = device_find(DEV_FLAG_RAMDISK, 1);
    assert(dev != EOF);

    op->mkfs(dev, 0);

    super_t *super = read_super(dev);
    inode_t *inode = namei("/dev");
    inode->mount = super;
    super->imount = inode;

    dev = device_find(DEV_FLAG_KEYBOARD, 1);
    mknod("/dev/keyboard", 0440 | IFCHR, dev);

    dev = device_find(DEV_FLAG_CONSOLE, 1);
    mknod("/dev/console", 0660 | IFCHR, dev);

    link("/dev/console", "/dev/stdout");
    link("/dev/keyboard", "/dev/stdin");

    file_t *file = &file_table[STDOUT_FILENO];
    inode = namei("/dev/stdout");
    file->inode = inode;
    file->mode = inode->mode;
    file->flags = O_WRONLY;
    file->offset = 0;

    file = &file_table[STDIN_FILENO];
    inode = namei("/dev/stdin");
    file->inode = inode;
    file->mode = inode->mode;
    file->flags = O_RDONLY;
    file->offset = 0;

    for (size_t i = 1; true; i++)
    {
        dev = device_find(DEV_FLAG_DISK, i);
        if (dev == EOF)
        {
            break;
        }
        device_t *device = device_get(dev);
        sprintf(buf, "/dev/%s", device->name);
        mknod(buf, IFBLK | 0660, dev);
    }

    for (size_t i = 1; true; i++)
    {
        dev = device_find(DEV_FLAG_PART, i);
        if (dev == EOF)
        {
            break;
        }
        device_t *device = device_get(dev);
        sprintf(buf, "/dev/%s", device->name);
        mknod(buf, IFBLK | 0660, dev);
    }

    for (size_t i = 1; true; i++)
    {
        dev = device_find(DEV_FLAG_SERIAL, i);
        if (dev == EOF)
        {
            break;
        }
        device_t *device = device_get(dev);
        sprintf(buf, "/dev/%s", device->name);
        mknod(buf, IFCHR | 0660, dev);
    }

    for (size_t i = 1; true; i++)
    {
        dev = device_find(DEV_FLAG_RAMDISK, i);
        if (dev == EOF)
        {
            break;
        }
        device_t *device = device_get(dev);
        sprintf(buf, "/dev/%s", device->name);
        mknod(buf, IFCHR | 0660, dev);
    }

    link("/dev/com1", "/dev/stderr");

    file = &file_table[STDERR_FILENO];
    inode = namei("/dev/stderr");
    file->inode = inode;
    file->mode = inode->mode;
    file->flags = O_WRONLY;
    file->offset = 0;
}